package com.kizuki.io;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.Pipe;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class TestNIO {

    /**
     * Channel-->只负责运输
     * Buffer-->数据
     *      缓冲区创建的时候分配的是直接缓冲区
     *      在FileChannel上调用map()方法，将文件直接映射到内存中创建
     */
    public long transferFileWithNIO(File from, File to) throws IOException {
        long start = System.currentTimeMillis();
        if (!to.exists()) {
            to.createNewFile();
        }

        RandomAccessFile read = new RandomAccessFile(from, "rw");
        RandomAccessFile write = new RandomAccessFile(to, "rw");
        FileChannel readChannel = read.getChannel();
        FileChannel writeChannel = write.getChannel();
        /**
         * 直接缓冲区和非直接缓冲区
         */
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
        ByteBuffer byteBufferDirect = ByteBuffer.allocateDirect(1024 * 1024);

        /**
         * 1. 通道数据存入缓冲区
         * 2. 切换读数据模式
         * 3. 将缓冲区数据存入通道中
         * 4. 清空缓冲区
         */
        while (readChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            writeChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        readChannel.close();
        writeChannel.close();
        long end = System.currentTimeMillis();
        return end - start;
    }

    public static void main(String[] args) {
        TestNIO testNIO = new TestNIO();
        File from = new File("F:\\from.txt");
        File to = new File("F:\\to.txt");
        try {
            long time = testNIO.transferFileWithNIO(from, to);
            System.out.println("NIO需要时间 = " + time + " ms");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }

    /**
     * 通道之间通过transfer()实现数据的传输(直接操作缓冲区)
     * 不一定能完整传输所有数据。
     */
    @Test
    public void testTransfer() throws IOException {
        FileChannel fromChannel = FileChannel.open(Paths.get("F:\\from.txt"), StandardOpenOption.READ);
        FileChannel toChannel = FileChannel.open(Paths.get("F:\\to.txt"), StandardOpenOption.WRITE);

        fromChannel.transferTo(0, fromChannel.size(), toChannel);
        fromChannel.close();
        toChannel.close();
    }

    /**
     * 内存映射文件
     */
    @Test
    public void testMappedByteBuffer() throws IOException {
        FileChannel fromChannel = FileChannel.open(Paths.get("F:\\from.txt"), StandardOpenOption.READ);
        FileChannel toChannel = FileChannel.open(Paths.get("F:\\to.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        MappedByteBuffer fromMap = fromChannel.map(FileChannel.MapMode.READ_ONLY, 0, fromChannel.size());
        // 是否直接缓冲区
        System.out.println(fromMap.isDirect());
        MappedByteBuffer toMap = toChannel.map(FileChannel.MapMode.READ_WRITE, 0, fromChannel.size());

        /**
         * 直接对缓冲区进行数据的读写操作
         */
        byte[] dst = new byte[fromMap.limit()];
        fromMap.get(dst);
        toMap.put(dst);
    }

    @Test
    public void testString() throws CharacterCodingException {
        CharBuffer buffer = CharBuffer.allocate(1024);
        String str = "测试CharBuffer";
        buffer.put(str);
        buffer.flip();

        /**
         * 编码
         */
        ByteBuffer encodeBuffer = Charset.forName("GBK").newEncoder().encode(buffer);
        /**
         * 解码
         */
        CharBuffer decodeBuffer = Charset.forName("GBK").newDecoder().decode(encodeBuffer);

        for (int i = 0, len = decodeBuffer.limit(); i < len; i++) {
            System.out.print(decodeBuffer.get());
        }
    }


    /**
     * 分散读取和聚集写入
     */
    @Test
    public void testScatterAndGather() throws IOException {
        ByteBuffer buff1 = ByteBuffer.allocate(100);
        ByteBuffer buff2 = ByteBuffer.allocate(1000);
        ByteBuffer[] buffs = {buff1, buff2};

        FileChannel fromChannel = FileChannel.open(Paths.get("F:\\from.txt"), StandardOpenOption.READ);
        fromChannel.read(buffs);

        for (int i = 0; i < buffs.length; i++) {
            ByteBuffer buff = buffs[i];
            buff.flip();
            System.out.println((i + 1) + " = " + new String(buff.array(), 0, buff.limit()));
        }

        FileChannel toChannel = FileChannel.open(Paths.get("F:\\to.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        toChannel.write(buffs);
    }


    /**
     * sinkChannel 读
     * sourceChannel 写
     */
    @Test
    public void testPipe() throws IOException {
        ByteBuffer buff = ByteBuffer.allocate(1024);
        buff.put("测试pipe".getBytes());
        buff.flip();

        Pipe pipe = Pipe.open();
        Pipe.SinkChannel sinkChannel = pipe.sink();
        sinkChannel.write(buff);

        Pipe.SourceChannel sourceChannel = pipe.source();
        buff.clear();
        int len = sourceChannel.read(buff);
        System.out.println(new String(buff.array(), 0, len));

        sourceChannel.close();
        sinkChannel.close();
    }

}
